/*
 * XReflectClass.java
 * 
 * Copyright (c) 2006 - 2007
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.xfc.util.reflect;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 * A helper class to make working with reflection and dynamically invoking methods
 * easier. Instead of throwing typed exceptions, any errors are thrown wrapped in
 * RuntimeExceptions.
 *
 * @author Devon Carew
 */
public class XReflectClass
{
    private Class clazz;
    
    
    public static XReflectClass createReflectClass(String className)
    {
        try
        {
            return createReflectClass(Class.forName(className));
        }
        catch (ClassNotFoundException cnfe)
        {
            return null;
        }
    }

    public static XReflectClass createReflectClass(Class clazz)
    {
        return new XReflectClass(clazz);
    }
    
    
    
    XReflectClass(Class clazz)
    {
        if (clazz == null)
            throw new IllegalArgumentException("Class parameter may not be null");
        
        this.clazz = clazz;
    }
    
    
    /**
     * @return the class that invocations are performed on
     */
    public Class getReflectClass()
    {
        return clazz;
    }
    
    
    public Object invokeStatic(String methodName)
    {
        return invokeStaticVarArgs(methodName, null);
    }
    
    public Object invokeStatic(String methodName, Object arg1)
    {
        return invokeStaticVarArgs(methodName, new Object[] { arg1 });
    }
    
    private static Object[] EMPTY_ARRAY = new Object[0];
    
    public Object invokeStaticVarArgs(String methodName, Object[] args)
    {
        if (args == null)
            args = EMPTY_ARRAY;
        
        Method method = XReflectClass.lookupMethod(clazz, methodName, args, true);
        
        if (method == null)
        	throw new RuntimeException("Unable to locate method " + methodName);
        
        try
        {
            return method.invoke(null, args);
        }
        catch (Throwable t)
        {
            if (t instanceof RuntimeException)
                throw (RuntimeException)t;
            
            throw new RuntimeException(t);
        }
    }
    
    // TODO: some new instance methods -
    
    
    // TODO: field access
    
    
    /**
	 * For now we just match based on the method name and the number of args. This may be improved on in the
	 * future.
	 * 
	 * @param clazz
	 * @param methodName
	 * @param args
	 * @throws SecurityException
	 * @throws NoSuchMethodException
	 * 
	 * @return Returns the desired method, or null if the method cannot be found.
	 */
    static Method lookupMethod(Class clazz, String methodName, Object[] args, boolean isStatic)
    {
        // TODO: just do a simple lookup, this can be made more sophisticated over time
        //       if necessary.
        
        Method[] methods = clazz.getDeclaredMethods();
        
        for (int i = 0; i < methods.length; i++)
        {
            Method method = methods[i];
            
            if (method.getName().equals(methodName) && Modifier.isStatic(method.getModifiers()) == isStatic)
            {
                if (method.getParameterTypes().length == args.length)
                {
                    try
                    {
                    	// Circumvent standard Java access checking.
                    	if (!method.isAccessible())
                    		method.setAccessible(true);
                    }
                	catch (SecurityException se) { }
                	
                	return method;
                }
            }
        }
        
        Class superClass = clazz.getSuperclass();
        
        return superClass == null ? null : lookupMethod(superClass, methodName, args, isStatic);
    }
    
    static Field lookupField(Class clazz, String fieldName, boolean isStatic)
    {
        Field field;
        
       	try
		{
			field = clazz.getDeclaredField(fieldName);
		}
       	catch (SecurityException e)
		{
			return null;
		}
       	catch (NoSuchFieldException e)
		{
       		if (!isStatic && clazz.getSuperclass() != null)
       			return lookupField(clazz.getSuperclass(), fieldName, isStatic);
       		
       		return null;
		}
        
       	// Circumvent standard Java access checking.
       	try
       	{
	       	if (!field.isAccessible())
	       		field.setAccessible(true);
        }
    	catch (SecurityException se) { }
    	
    	return field;
    }

}
